package com.baitiaojun.common.utils.reflect;

import cn.hutool.core.util.ObjectUtil;
import com.baitiaojun.common.constants.HttpStatus;
import com.baitiaojun.common.enums.impl.ServerExpEnums;
import com.baitiaojun.common.exception.ServerException;
import com.baitiaojun.common.utils.convert.ParamConvertUtils;
import com.baitiaojun.common.utils.date.DateTimeUtils;
import com.baitiaojun.common.utils.exception.ExceptionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Date;

/**
 * 反射工具, 用于方法的反射调用
 */
@Slf4j
public class ReflectUtils {

    /**
     * 指定对象名、名称、参数类型、参数值进行方法调用
     * @param objectName
     * @param methodName
     * @param paramTypes
     * @param params
     * @return
     * @param <T>
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T invoke(String objectName, String methodName, Class<?>[] paramTypes, Object[] params) {
        if (ObjectUtil.isNull(objectName) || StringUtils.isBlank(methodName)) {
            log.warn("调用的对象或方法不能为空");
            return null;
        }
        if (ObjectUtil.isNull(params) || params.length == 0) {
            log.warn("方法参数不能为空");
            return null;
        }
        Class<?> aClass;
        try {
            aClass = Class.forName(objectName);
        } catch (ClassNotFoundException e) {
            throw new ServerException(HttpStatus.ERROR, e.getMessage());
        }
        Method method = getMethodByParamType(aClass, methodName, paramTypes);
        if (ObjectUtil.isNull(method)) {
            log.warn("对象中调用的方法不存在");
            return null;
        }
        try {
            return (T) method.invoke(getInstance(aClass), params);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new ServerException(HttpStatus.ERROR, e.getMessage());
        }
    }

    /**
     * 根据参数类型、方法名查找对象的目标方法
     * @param objClass
     * @param methodName
     * @param paramTypes
     * @return
     */
    public static Method getMethodByParamType(Class<?> objClass, String methodName, Class<?>[] paramTypes) {
        if (ObjectUtil.isNull(objClass) || StringUtils.isBlank(methodName)) {
            log.warn("调用的对象或方法不能为空");
            return null;
        }
        //向上查找目标对象
        Method method = null;
        for (Class<?> aClass = objClass; !Object.class.equals(aClass); aClass = aClass.getSuperclass()) {
            try {
                method = aClass.getDeclaredMethod(methodName, paramTypes);
                method.setAccessible(true); //绕过检查器, 设置为public
                break;
            } catch (NoSuchMethodException e) {
                log.warn("方法无存在, {}", e.getMessage());
            }
        }
        return method;
    }

    /**
     * 根据目标对象、方法、参数值调用方法
     * @param objectName
     * @param methodName
     * @param params
     * @return
     * @param <T>
     */
    @SuppressWarnings({"unchecked"})
    public static <T> T invoke(String objectName, String methodName, Object[] params) {
        if (ObjectUtil.isNull(objectName) || StringUtils.isBlank(methodName)) {
            log.warn("调用的对象或方法不能为空");
            return null;
        }
        if (ObjectUtil.isNull(params) || params.length == 0) {
            log.warn("方法参数不能为空");
            return null;
        }
        Class<?> aClass;
        try {
            aClass = Class.forName(objectName);
        } catch (ClassNotFoundException e) {
            throw new ServerException(HttpStatus.ERROR, e.getMessage());
        }
        Method method = getMethodByParam(aClass, methodName, params);
        if (ObjectUtil.isNull(method)) {
            log.warn("对象中调用的方法不存在");
            return null;
        }
        Object[] newParams = convertParamsType(method.getParameterTypes(), params); //获取转换后的参数值
        try {
             return (T) method.invoke(getInstance(aClass), newParams);
        } catch (IllegalAccessException | InvocationTargetException e) {
            log.warn("反射异常: {}", ExceptionUtils.getExpMsg(e));
            throw new ServerException(ServerExpEnums.EXP_REFLECT_INVOKE);
        }
    }

    /**
     * 通过目标对象、目标方法、参数值查找调用的方法
     * @param objClass
     * @param methodName
     * @param params
     * @return
     */
    public static Method getMethodByParam(Class<?> objClass, String methodName, Object[] params) {
        if (ObjectUtil.isNull(objClass) || StringUtils.isBlank(methodName)) {
            log.warn("调用的对象或方法不能为空");
            return null;
        }
        //遍历目标对象
        Method ftargetMethod = null;
        for (Class<?> aClass = objClass; !aClass.equals(Object.class); aClass = aClass.getSuperclass()) {
            Method[] methods = aClass.getDeclaredMethods();
            //遍历目标方法
            for(Method method : methods) {
                if (method.getName().equals(methodName) && method.getParameterCount() == params.length) {
                    method.setAccessible(true); //设置为可访问, 同时关闭检查器
                    ftargetMethod = method;
                }
            }
        }
        return ftargetMethod;
    }

    public static Object getInstance(Class<?> clazz) {
        Object object;
        try {
            object = clazz.getDeclaredConstructor().newInstance();
        } catch (InstantiationException |
                 IllegalAccessException |
                 InvocationTargetException |
                 NoSuchMethodException e) {
            log.warn("无法创建对象");
            throw new ServerException(HttpStatus.ERROR, e.getMessage());
        }
        return object;
    }

    /**
     * 转为目标方法的参数类型
     * @param paramTypes
     * @return
     */
    public static Object[] convertParamsType(Class<?>[] paramTypes, Object[] params) {
        Object[] newParams = new Object[params.length];
        for (int i = 0; i < paramTypes.length; i++) {
            if (params[i] != null && params[i].getClass().equals(paramTypes[i])) {
                if (paramTypes[i].equals(String.class)) {
                    newParams[i] = ParamConvertUtils.convertStr(params[i]);
                } else if (paramTypes[i].equals(Long.class)) {
                    newParams[i] = ParamConvertUtils.convertLong(params[i]);
                } else if (paramTypes[i].equals(Integer.class)) {
                    newParams[i] = ParamConvertUtils.convertInteger(params[i]);
                } else if (paramTypes[i].equals(Double.class)) {
                    newParams[i] = ParamConvertUtils.convertDouble(params[i]);
                } else if (paramTypes[i].equals(Float.class)) {
                    newParams[i] = ParamConvertUtils.convertFloat(params[i]);
                } else if (paramTypes[i].equals(Date.class)) {
                    newParams[i] = DateTimeUtils.parseDate(params[i]);
                } else if (paramTypes[i].equals(Boolean.class)) {
                    newParams[i] = ParamConvertUtils.convertBoolean(params[i]);
                } else {
                    newParams[i] = params[i];
                }
            }
        }
        return newParams;
    }
}
